/**
 * Copyright (c) 2007-2008 Nearchos Paspallis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * AspectSense
 *
 * Middleware for annotation-based, context-aware and adaptive services for
 * mobile and ubiquitous computing
 *
 * This project is an open-source effort, initiated by Nearchos Paspallis as
 * part of his PhD work at the University of Cyprus (http://www.ucy.ac.cy).
 *
 * More information about the project is available at "http://aspectsense.org".
 */

package org.aspectsense.evaluators;

import org.aspectsense.metadata.ComponentMetadata;
import org.aspectsense.metadata.ServiceMetadata;
import org.aspectsense.metadata.MetadataFactory;
import org.aspectsense.metadata.ServiceUtilityMetadata;
import org.aspectsense.coreservices.ContextService;
import org.aspectsense.framework.CoreServices;

import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import java.util.logging.Logger;

/**
 * @author Nearchos Paspallis [nearchos@cs.ucy.ac.cy]
 *         Date: Jan 3, 2008
 *         Time: 11:16:18 AM
 */
public class DefaultEvaluators implements Evaluators
{
    private static final UtilityEvaluator DUMMY_UTILITY_EVALUATOR = new UtilityEvaluator() {
        public double evaluateDouble() throws EvaluationException {
            return 0.5d;
        }
    };

    private final Logger logger = Logger.getLogger(DefaultEvaluators.class.getCanonicalName());

    private final ContextService contextService = CoreServices.getContextService();

    private final DefaultParser parser;

    // ----------------- Singleton pattern implementation ------------------- //

    private static DefaultEvaluators instance = null;

    /**
     * Provides access to the lazy-loaded singleton instance of the
     * {@link DefaultEvaluators}.
     *
     * @return an instance of
     * {@link DefaultEvaluators}
     */
    public static DefaultEvaluators getInstance()
    {
        synchronized(DefaultEvaluators.class)
        {
            if(instance == null) instance = new DefaultEvaluators();

            return instance;
        }
    }

    private DefaultEvaluators()
    {
        logger.fine("Starting instance of "
                + DefaultEvaluators.class.getCanonicalName());

        parser = new DefaultParser(this, contextService);
    }

    // --------------- End of singleton pattern implementation -------------- //

    /** Mapping service IDs to the corresponding utility evaluator */
    private final Map<String, Map<String, UtilityEvaluator>> serviceUtilityEvaluators
            = new HashMap<String, Map<String, UtilityEvaluator>>();

    private final Map<String, Map<String, Class>> serviceUtilityEvaluatorTypes
            = new HashMap<String, Map<String, Class>>();

    public void addServiceProvider(final ComponentMetadata componentMetadata, final Class componentType)
            throws ParserException
    {
        if(componentMetadata == null)
        {
            throw new NullPointerException("Illegal null argument");
        }

        final String componentID
                = componentMetadata.getComponentTypeCanonicalName();

        final ServiceMetadata providedServiceMetadata
                = componentMetadata.getProvidedService();
        final String providedServiceID
                = providedServiceMetadata.getServiceTypeCanonicalName();

        // updating the providersEvaluators
        Map<String, UtilityEvaluator> providersEvaluators
                = serviceUtilityEvaluators.get(providedServiceID);

        if(providersEvaluators == null)
        {
            providersEvaluators = new HashMap<String, UtilityEvaluator>();
            serviceUtilityEvaluators.put(providedServiceID, providersEvaluators);
        }

        if(providersEvaluators.containsKey(componentID))
        {
            throw new IllegalArgumentException("The specified component is "
                    + "already included in the evaluators map.");
        }
        // done updating the providersEvaluators!

        // updating the providersEvaluatorTypes
        Map<String, Class> providersEvaluatorTypes
                = serviceUtilityEvaluatorTypes.get(providedServiceID);

        if(providersEvaluatorTypes == null)
        {
            providersEvaluatorTypes = new HashMap<String, Class>();
            serviceUtilityEvaluatorTypes.put(providedServiceID, providersEvaluatorTypes);
        }

        assert !providersEvaluatorTypes.containsKey(componentID);
        // done updating the providersEvaluatorTypes

        final ServiceUtilityMetadata serviceUtilityMetadata
                = MetadataFactory.getServiceUtilityMetadata(componentType );

        UtilityEvaluator utilityEvaluator;

        final Class evaluatorType = MetadataFactory.getCachedEvaluatorType(componentType);

        if(evaluatorType.equals(UtilityEvaluator.class))
        {
            final String [] constants = serviceUtilityMetadata.getConstants();
            final Map<String, String> constantsMap = new HashMap<String, String>();
            for(int i = 0; (i + 1) < constants.length; i = i + 2)
            {
                final String key = constants[i];
                final String value = constants[i + 1];
                constantsMap.put(key, value);
            }

            final String utilityFunctionS = serviceUtilityMetadata.getUtilityFunction();
            try
            {
                utilityEvaluator = parser.parseUtilityEvaluator(constantsMap, utilityFunctionS);
            }
            catch (ParserException pe)
            {
                logger.severe("Error while parsing the evaluator of component: " + componentID);

                utilityEvaluator = DUMMY_UTILITY_EVALUATOR;
            }
        }
        else
        {
            throw new RuntimeException("Unknown utility type: " + evaluatorType);
        }

        providersEvaluators.put(componentID, utilityEvaluator);
        providersEvaluatorTypes.put(componentID, evaluatorType);
    }

    public void removeServiceProvider(final ComponentMetadata componentMetadata)
    {
        if(componentMetadata == null)
        {
            throw new NullPointerException("Illegal null argument");
        }

        final String componentID
                = componentMetadata.getComponentTypeCanonicalName();

        final ServiceMetadata providedServiceMetadata
                = componentMetadata.getProvidedService();
        final String providedServiceID
                = providedServiceMetadata.getServiceTypeCanonicalName();

        Map<String, UtilityEvaluator> providersEvaluators
                = serviceUtilityEvaluators.get(providedServiceID);

        if(providersEvaluators == null)
        {
            throw new IllegalArgumentException("Could not locate providers for"
                    + " the service: " + providedServiceID);
        }

        providersEvaluators.remove(componentID);
    }

    public UtilityEvaluator getUtilityEvaluator(final String serviceID, final String componentID)
    {
        if(serviceID == null || componentID == null)
        {
            throw new NullPointerException("Illegal null arguments");
        }

        final Map<String, UtilityEvaluator> utilityEvaluators
                = serviceUtilityEvaluators.get(serviceID);

        if(utilityEvaluators == null)
        {
            throw new IllegalArgumentException("The specified serviceID does"
                    + " not define any utility evaluators: " + serviceID);
        }

        final UtilityEvaluator utilityEvaluator
                = utilityEvaluators.get(componentID);

        if(utilityEvaluator == null)
        {
            throw new IllegalArgumentException("The specifies component is not"
                    + " registered as a provider for the specified service!");
        }

        return utilityEvaluator;
    }

    public boolean containsService(String serviceID)
    {
        return serviceUtilityEvaluators.containsKey(serviceID);
    }

    public double getServiceUtility(String serviceID)
            throws EvaluationException
    {
        final Collection<UtilityEvaluator> serviceProvidersUtilityEvaluators
                = serviceUtilityEvaluators.get(serviceID).values();

        double maxServiceUtility = -1;

        for(final UtilityEvaluator utilityEvaluator : serviceProvidersUtilityEvaluators)
        {
            final double currentServiceUtility = utilityEvaluator.evaluateDouble();
            if(currentServiceUtility > maxServiceUtility)
            {
                maxServiceUtility = currentServiceUtility;
            }
        }

        return maxServiceUtility;
    }
}